﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;
using System.Linq;

namespace HuffmanClasses
{
    [Serializable]
    public class Frequency : IComparable
    {
        private byte _symbol;
        private String _stringCode;
        private long _count;
        public string StringCode
        {
            get { return _stringCode; }
            set { _stringCode = value;}
        }

        public long Count
        {
            get { return _count; }
            set { _count = value; }
        }

        public byte Symbol
        {
            get { return _symbol; }
            set { _symbol = value; }
        }

        public Frequency(byte symbol = (byte)0, int count = 0)
        {
            _count = count;
            _symbol = symbol;
        }

        static public bool operator < (Frequency lhs, Frequency rhs)
        {
            return lhs._count < rhs._count;
        }

        static public bool operator >(Frequency lhs, Frequency rhs)
        {
            return lhs._count > rhs._count;
        }

        static public bool operator == (Frequency lhs, Frequency rhs)
        {
            return lhs._count == rhs._count;
        }

        static public bool operator !=(Frequency lhs, Frequency rhs)
        {
            return lhs._count != rhs._count;
        }

        public int CompareTo(object lhs)
        {
            int result = 0;
            if (this < (Frequency)lhs) 
                result = -1;
            else if (this > (Frequency)lhs)
                result = 1;
            return result;
        }
    }

    internal class TreeNode : IComparable
    {
        private Frequency _frequency;
        public Frequency Frequency
        {
            get { return _frequency; }
            set { _frequency = value; }
        }

        private TreeNode _left;
        public TreeNode Left
        {
            get { return _left; }
            set { _left = value; }
        }

        private TreeNode _right;
        public TreeNode Right
        {
            get { return _right; }
            set { _right = value; }
        }

        public TreeNode(Frequency frequency)
        {
            _frequency = frequency;
        }

        public TreeNode()
        {
            _frequency = new Frequency();
        }
        public int CompareTo(object rhs)
        {
            return _frequency.CompareTo(((TreeNode)rhs).Frequency);
        }
    }

    [Serializable]
    public class Alphabet : ICloneable
    {
        private Dictionary<byte, Frequency> _frequencies = new Dictionary<byte, Frequency>();

        [NonSerialized]
        private TreeNode _root;

        [NonSerialized]
        private TreeNode _currentNode;

        public Alphabet()
        {
        }

        public Alphabet(Alphabet src)
        {
            _frequencies = new Dictionary<byte, Frequency>(src._frequencies);
        }

        public Frequency this[byte key]
        {
            get
            {
                if (!_frequencies.ContainsKey(key))
                    _frequencies.Add(key, new Frequency(key));
                return _frequencies[key];
            }
        }

        private void TreeDescent(TreeNode node, string code)
        {
            if (node.Left == null && node.Right == null)
            {
                node.Frequency.StringCode = code;
                _frequencies[node.Frequency.Symbol] = node.Frequency;
            }
            else
            {
                TreeDescent(node.Left, code + "0");
                TreeDescent(node.Right, code + "1");
            }
        }

        public bool PushNextBit(bool bit, out byte symbol)
        {
            if (_root == null)
            {
                CalculateCodes();
            }
            symbol = 0;
            bool result = false;
            _currentNode = bit ? _currentNode.Right : _currentNode.Left;
            if (_currentNode.Left == null && _currentNode.Right == null)
            {
                symbol = _currentNode.Frequency.Symbol;
                result = true;
                _currentNode = _root;
            }
            return result;
        }

        public void ResetCurrentNode()
        {
            _currentNode = _root;
        }

        public void CalculateCodes()
        {
            BuildTree();
            TreeDescent(_root, "");
        }

        public void CalculateCodes(byte[] data)
        {
            for (int i = 0; i < data.Count(); ++i)
            {
                this[data[i]].Count++;
            }
            CalculateCodes();
        }


        public void BuildTree()
        {
            C5.IPriorityQueue<TreeNode> queue = new C5.IntervalHeap<TreeNode>();
            foreach (var pair in _frequencies)
            {
                queue.Add(new TreeNode(pair.Value));
            }

            int n = queue.Count;
            for (int i = 1; i != n; ++i)
            {
                TreeNode node = new TreeNode();
                node.Left = queue.DeleteMin();
                node.Right = queue.DeleteMin();
                node.Frequency.Count = node.Left.Frequency.Count + node.Right.Frequency.Count;
                queue.Add(node);
            }
            _root = queue.DeleteMin();
            _currentNode = _root;
        }
        //public FrequencyStruct[] AsBinary()
        //{
        //    FrequencyStruct[] result = new FrequencyStruct[_frequencies.Count];
        //    int i = 0;
        //    foreach (var frequency in _frequencies)
        //    {
        //        result[i] = new FrequencyStruct(frequency.Value);
        //    }
        //    return result;
        //}

        public object Clone()
        {
            return new Alphabet(this);
        }
    }

    [Serializable]
    public class HuffmanCompressedData
    {
        private byte[] _data; // Сжатые данные
        private long _sourceLenght;
        private Alphabet _alphabet; // Алфавит сжатия
       

        public HuffmanCompressedData(byte[] data, long sourceLenght, Alphabet alphabet)
        {
            _data = data;
            _sourceLenght = sourceLenght;
            _alphabet = alphabet;
        }

        public byte[] Data
        {
            get { return _data; }
        }

        public Alphabet Alphabet
        {
            get { return _alphabet; }
        }

        public long SourceLenght
        {
            get { return _sourceLenght; }
        }
    }

    public interface ICompressor
    {
        HuffmanCompressedData Compress(byte[] data);
    }

    public class HuffmanCompressor : ICompressor
    {
        private String _buffer = "";
        private int _bufferSize = 1024;
        private Alphabet _alphabet = new Alphabet();

        public HuffmanCompressor()
        {
        }

        public HuffmanCompressedData Compress(byte[] data)
        {
            _alphabet.CalculateCodes(data);
            byte[] result = new byte[data.Count()];
            int resultCount = 0;

            for (int i = 0; i < data.Count(); ++i)
            {
                string code = _alphabet[data[i]].StringCode;
                _buffer += code;
                int symbolsToCut = (_buffer.Length / _bufferSize) * _bufferSize;
                if (i == data.Count() - 1)
                {
                    symbolsToCut = _buffer.Length;
                }

                if (symbolsToCut > 0)
                {
                    BitArray bitArray = new BitArray(symbolsToCut);
                    for (int j = 0; j < symbolsToCut; ++j)
                    {
                        bitArray[j] = (_buffer[j] == '1');
                    }
                    _buffer = _buffer.Remove(0, symbolsToCut);
                    bitArray.CopyTo(result, resultCount);
                    if (bitArray.Count % 8 !=0) 
                        resultCount += bitArray.Count / 8 + 1;
                    else
                        resultCount += bitArray.Count / 8;
                }
            }
            _buffer = "";
            Array.Resize(ref result, resultCount);

            return new HuffmanCompressedData(result, data.Count(), (Alphabet)_alphabet.Clone());
        }
    }

    public interface IDecompressor
    {
        byte[] Decompress(HuffmanCompressedData compressedData);
    }

    public class HuffmanDecompressor : IDecompressor
    {
        private int _tempBytes = 8;
        public byte[] Decompress(HuffmanCompressedData compressedData)
        {
            byte[] result = new byte[compressedData.SourceLenght];
            long resultIndex = 0;
            string bitBuffer = "";
            byte[] tempArray = new byte[_tempBytes];
            long count = compressedData.Data.Count();
            for (int i = 0; i < count; i += _tempBytes)
            {
                for (int j = 0; j < _tempBytes && i + j < count; ++j)
                {
                    tempArray[j] = compressedData.Data[i + j];
                    if (i + j == count - 1)
                    {
                        Array.Resize(ref tempArray, j + 1);
                    }

                }

                BitArray bitArray = new BitArray(tempArray);

                for (int j = 0; j != bitArray.Count; ++j)
                {
                    bitBuffer += bitArray[j] ? "1" : "0";
                }
                byte symbol;
                int k = 0;
                while (k < bitBuffer.Length)
                {
                    if (compressedData.Alphabet.PushNextBit(bitBuffer[k] == '1' ? true : false, out symbol))
                    {
                        bitBuffer = bitBuffer.Remove(0, k + 1);
                        if (resultIndex >= compressedData.SourceLenght)
                            break;
                        result[resultIndex] = symbol;
                        resultIndex++;
                        k = 0;
                    }
                    else
                    {
                        k++;
                    }

                }
                compressedData.Alphabet.ResetCurrentNode(); //сбрасываем путь по дереву, иначе если мы прошли часть пути по дереву, 
                //но не дошли до листа с символом и выполнился выход из цикла по условию k < bitBuffer.Length, то потом мы снова будет класть старые биты, 
                //которые были в начале буфера, но идти будем уже не от корня дерева, что неверно
            }
            return result;
        }
    }

    public interface IArchiveWriter
    {
        void PackFiles(List<String> sourceFiles, String archiveName, ICompressor compressor);
    }

    public class HuffmanSolidArchiveWriter : IArchiveWriter
    {
        public void PackFiles(List<String> sourceFiles, String archiveName, ICompressor compressor)
        {
            FileStream outStream = new FileStream(archiveName, FileMode.Create);
            MemoryStream memoryStream = new MemoryStream();
            List<KeyValuePair<String, long>> fileInfos = new List<KeyValuePair<string, long>>();

            long allSize = 0;

            foreach (var sourceFile in sourceFiles)
            {

                try
                {
                    String fileName = Path.GetFileName(sourceFile);
                    //StreamReader streamReader = new StreamReader(sourceFile);
                    FileInfo fileInfo = new FileInfo(sourceFile);
                    fileInfos.Add(new KeyValuePair<string, long>(fileName, fileInfo.Length));
                    allSize += fileInfo.Length;
                }
                catch (Exception e)
                {
                    MessageBox.Show("Не могу обработать файл " + sourceFile + "\nОшибка: " + e.Message);
                }
            }

            byte[] data = new byte[allSize];
            int offset = 0;
            foreach (var sourceFile in sourceFiles)
            {
                FileStream fileStream = new FileStream(sourceFile, FileMode.Open);
                fileStream.Read(data, offset, (int)fileStream.Length);
                offset += (int)fileStream.Length;
            }
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(outStream, fileInfos.Count);
            foreach (var pair in fileInfos)
            {
                binaryFormatter.Serialize(outStream, pair);
            }
            HuffmanCompressedData compressedData = compressor.Compress(data);

            binaryFormatter.Serialize(outStream, compressedData);
            //string result = Convert.ToBase64String(memoryStream.ToArray());
            //byte[] resultArray = Encoding.UTF8.GetBytes(result.ToCharArray());
            //outStream.Write(resultArray, 0, resultArray.Count());
            outStream.Close();
        }
    }

    public interface IArchiveReader
    {
        List<KeyValuePair<String, long>> GetFileInfos();
        void UnPackToFolder(String path, IDecompressor decompressor);
    }

    public class HuffmanSolidArchiveReader : IArchiveReader
    {
        private FileStream _inStream;
        private List<KeyValuePair<String, long>> _fileInfos = new List<KeyValuePair<string, long>>();
        private HuffmanCompressedData _compressedData;
        public HuffmanSolidArchiveReader(String archiveFileName)
        {
            _inStream = new FileStream(archiveFileName, FileMode.Open);
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            int count = (int)binaryFormatter.Deserialize(_inStream);
            for (int i = 0; i != count; ++i)
            {
                _fileInfos.Add((KeyValuePair<String, long>)binaryFormatter.Deserialize(_inStream));
            }
        }
        public List<KeyValuePair<String, long>> GetFileInfos()
        {
            return _fileInfos;
        }

        public void UnPackToFolder(String path, IDecompressor decompressor)
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            _compressedData = (HuffmanCompressedData)binaryFormatter.Deserialize(_inStream);

            byte[] sourceData = decompressor.Decompress(_compressedData);

            int offset = 0;
            foreach (var fileInfo in _fileInfos)
            {
                FileStream outStream = new FileStream(path + "\\" + fileInfo.Key, FileMode.Create);

                outStream.Write(sourceData, offset, (int)fileInfo.Value);
                offset += (int)fileInfo.Value;
                outStream.Close();
            }

        }
    }


}